import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:flutter_settings_screens/flutter_settings_screens.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter/material.dart';
import 'package:macless_haystack/accessory/accessory_list_item.dart';
import 'package:macless_haystack/preferences/user_preferences_model.dart';
import 'package:latlong2/latlong.dart';
import 'package:logger/logger.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:http/http.dart' as http;

import 'package:macless_haystack/location/place_info.dart';

class LocationModel extends ChangeNotifier {
  LatLng? here;
  // geocode.Placemark? herePlace;
  Placeinfo? herePlace;
  // StreamSubscription<LocationData>? locationStream;
  StreamSubscription<Position>? locationStream;
  bool initialLocationSet = false;
  //Location location = Location();

  var logger = Logger(
    printer: PrettyPrinter(methodCount: 0),
  );

  /// Requests access to the device location from the user.
  ///
  /// Initializes the location services and requests location
  /// access from the user if not granged.
  /// Returns if location access was granted.
  Future<bool> requestLocationAccess() async {
    bool serviceEnabled;
    LocationPermission permissionGranted;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      /*serviceEnabled = await location.requestService();
      if (!serviceEnabled) {
        return false;
      }*/
      openAppSettings();
      return false;
    }
    if (await Permission.location.isPermanentlyDenied) {
      openAppSettings();
      return false;
    }

    permissionGranted = await Geolocator.checkPermission();
    if (permissionGranted == LocationPermission.denied) {
      permissionGranted = await Geolocator.requestPermission();
      if (permissionGranted == LocationPermission.denied ||
          permissionGranted == LocationPermission.deniedForever) {
        return false;
      }
    }

    return true;
  }

  /// Requests location updates from the platform.
  ///
  /// Listeners will be notified about location changes.
  Future<void> requestLocationUpdates() async {
    var permissionGranted = await requestLocationAccess();
    if (permissionGranted) {
      // Handle future location updates
      // locationStream ??= location.onLocationChanged.listen(_updateLocation);
      locationStream ??= Geolocator.getPositionStream().listen((Position pos) {
        _updateLocation(pos, reGeo: false);
      });

      //var locationData = await location.getLocation();
      var locationData = await Geolocator.getCurrentPosition();

      // Fetch the current location
      if (locationData != null) _updateLocation(locationData);
    } else {
      initialLocationSet = true;
      if (locationStream != null) {
        locationStream?.cancel();
        locationStream = null;
      }
      _removeCurrentLocation();
      notifyListeners();
    }
  }

  /// Updates the current location if new location data is available.
  ///
  /// Additionally updates the current address information to match
  /// the new location.
  void _updateLocation(Position locationData, {bool reGeo = true}) {
    if (locationData.latitude != null && locationData.longitude != null) {
      logger.d(
          'Location here: ${locationData.latitude!}, ${locationData.longitude!}');

      here = LatLng(locationData.latitude!, locationData.longitude!);
      initialLocationSet = true;
      if (reGeo) {
        reGeoCode(wgs84ToGcj02(here!)).then((value) {
          herePlace = value;
          notifyListeners();
        });
      } else {
        notifyListeners();
      }
    } else {
      logger.e('Received invalid location data: $locationData');
    }
    notifyListeners();
  }

  /// Cancels the listening for location updates.
  void cancelLocationUpdates() {
    if (locationStream != null) {
      locationStream?.cancel();
      locationStream = null;
    }
    _removeCurrentLocation();
    notifyListeners();
  }

  /// Resets the currently stored location and address information
  void _removeCurrentLocation() {
    here = null;
    herePlace = null;
  }

  /// Returns the address for a given geolocation (latitude & longitude).
  ///
  /// Only works on mobile platforms with their local APIs.
  Future<Placeinfo?> getAddress(LatLng? location) async {
    if (location == null) {
      return null;
    }
    /*double lat = location.latitude;
    double lng = location.longitude;

    try {
      List<Placeinfo> placemarks =
          await geocode.placemarkFromCoordinates(lat, lng);
      return placemarks.first;
    } on MissingPluginException {
      return null;
    } on PlatformException {
      return null;
    }*/

    /*var convertedLocations = await convertCoordType([location]);
    if (convertedLocations != null) {
      var convertedLocation = convertedLocations[0];
      return reGeoCode(convertedLocation);
    }
    return null;*/
    var convertedLocation = wgs84ToGcj02(location);
    return reGeoCode(convertedLocation);
  }

  Future<Placeinfo?> reGeoCode(LatLng? location) async {
    if (location == null) {
      return null;
    }
    String? amapKey = Settings.getValue<String>(amapWebApiKey);
    final response = await http.get(Uri.parse(
        'https://restapi.amap.com/v3/geocode/regeo?key=${amapKey}&location=${location.longitude},${location.latitude}'));

    if (response.statusCode == 200) {
      Map<String, dynamic> data = json.decode(response.body);
      if (data['status'] == '1') {
        var item = data['regeocode'];
        Map<String, dynamic> addcmp = item['addressComponent'];
        Map<String, dynamic>? streetcmp = item['streetNumber'];

        return Placeinfo.fromMap({
          'name': item['formatted_address'],
          'country': addcmp['country'],
          'city': addcmp['city'],
          'province': addcmp['province'],
          'district': addcmp['district'],
          'township': addcmp['township'],
          'street': streetcmp != null ? streetcmp['street'] : ''
        });
      }
    }
    return null;
  }

  /*Future<List<LatLng>?> convertCoordType(List<LatLng>? locations) async {
    if (locations == null || locations.isEmpty) {
      return null;
    }
    final response = await http.get(Uri.parse(
        'https://restapi.amap.com/v3/assistant/coordinate/convert?key=${amapKey}&coordsys=gps&locations=${locations.map((x) => '${x.longitude},${x.latitude}').join('|')}'));

    if (response.statusCode == 200) {
      Map<String, dynamic> data = json.decode(response.body);
      if (data['status'] == '1') {
        var str = data['locations'] as String;
        var arr = str.split(';').where((x) => x != '');

        return arr.map((x) {
          var ll = x.split(',');
          return LatLng(double.parse(ll[1]), double.parse(ll[0]));
        }).toList();
      }
    }
    return null;
  }*/

  // WGS84 to GCJ02
  LatLng? wgs84ToGcj02(LatLng? location) {
    if (location == null) {
      return null;
    }
    const double pi = 3.1415926535897932384626;
    const double a = 6378245.0; // 长半轴
    const double ee = 0.00669342162296594323; // 偏心率平方
    double dLat, dLon;

    double lat = location.latitude;
    double lon = location.longitude;

    if (outOfChina(lat, lon)) {
      return location; // 如果不在中国, 不转换
    }

    dLat = _transformLat(lon - 105.0, lat - 35.0);
    dLon = _transformLon(lon - 105.0, lat - 35.0);
    double radLat = lat / 180.0 * pi;
    double magic = sin(radLat);
    magic = 1 - ee * magic * magic;
    double sqrtMagic = sqrt(magic);
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
    dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);

    double mgLat = lat + dLat;
    double mgLon = lon + dLon;

    return LatLng(mgLat, mgLon);
  }

  bool outOfChina(double lat, double lon) {
    return (lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271);
  }

  double _transformLat(double x, double y) {
    const double pi = 3.1415926535897932384626;
    double ret = -100.0 +
        2.0 * x +
        3.0 * y +
        0.2 * y * y +
        0.1 * x * y +
        0.2 * sqrt(x.abs());
    ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
    ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
    ret +=
        (160.0 * sin(y / 12.0 * pi) + 320.0 * sin(y * pi / 30.0)) * 2.0 / 3.0;
    return ret;
  }

  double _transformLon(double x, double y) {
    const double pi = 3.1415926535897932384626;
    double ret =
        300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x.abs());
    ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
    ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
    ret +=
        (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
    return ret;
  }
}
